package lwf.handler;

import java.util.ArrayList;
import java.util.List;

import net.lwf.entity.AttemptedQuestion;
import net.lwf.entity.QuestionInfo;
import net.lwf.exception.ExceptionUtil;
import net.lwf.handler.constant.IHandlerConstant;
import net.lwf.number.util.NumberFormatter;
import net.lwf.pdf.IPdfConstant;
import net.lwf.pdf.util.FontUtil;


import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;


public abstract class ProcessHandler implements IProcessHandler {
	
	private String data1;
	private String data2;
	private String operation = null;
	private String answer;
	private String useranswer;
	private boolean answered = false;
	private boolean correctAnswer = false;
	private int decimalDigits = 0;
	private int nAttempts = 0;
	private List<String> adddata;
	private String questionNum;
	private long starttime;
	private long endtime;
	private int userAttemptLimit = 0;
	private boolean formatUserAnswer = false; //to number format to check the answer
	
	//------------------------------------- New ones
	private String grade;
	private String baseImagePath;
	
	public ProcessHandler(String grade) {
		this.grade = grade;
	}
	
	public ProcessHandler() {
	}
	
	/**
	 * Generates one problem that contain question and answer.
	 * 
	 * @return
	 */
	public abstract QuestionInfo generateSingle();
	
	/**
	 * Generates multiple problems that contain question and answer.
	 * 
	 * @return
	 */
	public abstract List<QuestionInfo> generateMultiple();
	
	public String getGrade() {
		return grade;
	}
	
	public String getBaseImagePath() {
		return baseImagePath;
	}

	public void setBaseImagePath(String baseImagePath) {
		this.baseImagePath = baseImagePath;
	}

	/**
	 * @return Returns the decimalDigits.
	 */
	public int getDecimalDigits() {
		return decimalDigits;
	}

	/**
	 * @param decimalDigits The decimalDigits to set.
	 */
	public void setDecimalDigits(int decimalDigits) {
		this.decimalDigits = decimalDigits;
	}
	
	/**
	 * @return Returns the useranswer.
	 */
	public String getUseranswer() {
		//return NumberFormatter.format(useranswer, displayFormat());
		return this.useranswer;
	}
	/**
	 * @param useranswer The useranswer to set.
	 */
	public void setUseranswer(String useranswer) {
		this.useranswer = useranswer;
	}

	public boolean matchAnswer(String useranswer, String actualanswer) {

		if(!isFormatUserAnswer()) {
			if(this.useranswer.trim().equals(actualanswer.trim())) {
				return true;
			}	
			else {
				return false;
			}
		}
		String formmattedUA = NumberFormatter.format(useranswer, getDecimalDigits());
		String formmattedAA = NumberFormatter.format(actualanswer, getDecimalDigits());
		if(formmattedUA.equals(formmattedAA)) {
			return true;
		}
		return false;
	}
	
	public boolean matchAnswer(String useranswer, AttemptedQuestion aq) {
		if(useranswer.trim().equals(aq.getAnswer().trim())) {
			return true;
		}
		return false;
	}
	//--------------------------------------------------------------------------- For PDF generation
	
	/**
	 * This method enables to add the content directly from the handler.
	 * 
	 * @param document	Document instance
	 * 
	 * @throws DocumentException
	 */
	public abstract void getContent(Document document) throws DocumentException;
	
	//public abstract Element processCellData(QuestionInfo question);

	/**
	 * Returns number of data rows for the pdf document. Each row is represents a problem or question.
	 * If not impremented, system considers default value which is one.
	 * 
	 * @return
	 */
	public abstract int getRows();

	/**
	 * Returns number of data columns for the pdf document. Each column is represents a problem or question.
	 * If not impremented, system considers default value which is one.
	 * 
	 * @return
	 */
	public abstract int getColumns();
	
	/**
	 * This is to be implemented by the handler. The return object contains problems/questions for
	 * the pdf document.
	 * 
	 * @return
	 */
	//public abstract List<?> getTabularData();
	
	/**
	 * This implemnted method determines whether the generated PDF document is portrait form or
	 * landscape form.
	 * 
	 * @return
	 */
	public abstract boolean isPortrait();
	
	public abstract String getPdfInstructionalText();
	
	public abstract String getPdfNote();
	
	/**
	 * @see net.lwf.handler.IProcessHandler#getDocument()
	 */
	public Document getDocument() {
		if(isPortrait()) {
			return new Document(PageSize.A4, IPdfConstant.PAGE_LEFT_SPACE, IPdfConstant.PAGE_RIGHT_SPACE, IPdfConstant.PAGE_TOP_SPACE, IPdfConstant.PAGE_BOTTOM_SPACE); 
		}
		return new Document(PageSize.A4.rotate(), IPdfConstant.PAGE_LEFT_SPACE, IPdfConstant.PAGE_RIGHT_SPACE, IPdfConstant.PAGE_TOP_SPACE, IPdfConstant.PAGE_BOTTOM_SPACE); 
	}
	
	/**
	 * @see net.lwf.handler.IProcessHandler#generate(com.itextpdf.text.Document)
	 */
	public void generate(Document document) {
		List<QuestionInfo> tabulardata = generateMultiple();
		document.open();
		try {
			
			if(tabulardata != null) {
				this.processTabularData(document, tabulardata);
			}
			else {
				getContent(document);
			}
			//--------------------------------------------------------------- New Page for answers only
			document.newPage();
			document.add(new Paragraph(" "));
			PdfPTable table = new PdfPTable(2);
			table.setSpacingBefore(20);
			Font tableheader = FontFactory.getFont(FontFactory.HELVETICA, 20, Font.BOLD, new BaseColor(0, 0, 0));
			table.addCell(new Phrase("Question Number", tableheader));
			table.addCell(new Phrase("Answer", tableheader));
			int loopCounter = 0;
			for(QuestionInfo info: tabulardata) {
				loopCounter++;
				table.addCell(new Phrase(String.valueOf(loopCounter)));
				if(info.getFormattedAnswer() != null) {
					table.addCell(new Phrase(info.getFormattedAnswer()));
				}
				else {
					table.addCell(new Phrase(info.getAnswer()));
				}
				
			}
			document.add(table);			
		} catch (DocumentException dex) {
			ExceptionUtil.logAndThrowException(ProcessHandler.class, dex);
		}

		document.close();
	}
	
	//@TODO Change this to abstract method --> 09/12/11
	public Element processCellData(QuestionInfo question) {
		PdfPTable table = new PdfPTable(1);
		PdfPCell cell = new PdfPCell(new Phrase(question.getQuestion()));
		cell.setBorder(1);
		cell.setHorizontalAlignment(Element.ALIGN_CENTER);
		table.addCell(cell);
		return table;
	}
	
	private void processTabularData(Document document, List<QuestionInfo> tabulardata) throws DocumentException {
		document.add(new Phrase(" "));
		int columns = getColumns() == 0 ? IPdfConstant.DEFAULT_COLUMNS : getColumns();
		int rows = getRows() == 0 ? IPdfConstant.DEFAULT_ROWS : getRows();
		int datasize = tabulardata.size();
		
		PdfPTable table = new PdfPTable(columns);
		table.setWidthPercentage(100.f);
		table.setSpacingBefore(10);
		//table.setExtendLastRow(true);
		//Instructional Text
		PdfPCell cell = new PdfPCell(new Phrase(getPdfInstructionalText(), FontUtil.getDisplayFont(16)));
        cell.setBorder(0);
        cell.setColspan(columns);
        table.addCell(cell);

		int dataindex = 0;
		for(int i=0; i<rows; i++) {
			for(int j=0; j<columns; j++) {
				if(dataindex+1 <= datasize) {
					QuestionInfo qinfo = (QuestionInfo) tabulardata.get(dataindex);
					PdfPTable tablecell = (PdfPTable) processCellData(qinfo);
					table.addCell(tablecell);
				}
				else {
					table.addCell(new Phrase(" "));
				}
				dataindex++;
			}
			//table.completeRow();
			
		}
		//Add Note if any
		if(getPdfNote() != null) {
			//Phrase phrase = new Phrase("Note: ", FontUtil.getBoldedDisplayFont(16));
			Phrase phrase = new Phrase("Note: " + getPdfNote(), FontUtil.getBoldedDisplayFont(16));
			cell = new PdfPCell();
			cell.setPaddingTop(5);
//			cell.addElement(phrase);
//			phrase = new Phrase(getPdfNote(), FontUtil.getDisplayFont(16));
			cell.addElement(phrase);
	        cell.setBorder(0);
	        cell.setColspan(columns);
	        table.addCell(cell);
		}
		table.completeRow();
		
		document.add(table);
	}
	
	//---------------------------------------------------------- End of methods - For PDF generation

	
	//------------------------------------------------------------------------------------------------
	//Old Methods to be removed once refactored
	//------------------------------------------------------------------------------------------------
	/**
	 * @return Returns the correctAnswer.
	 */
	public boolean getCorrectAnswer() {
		return correctAnswer;
	}
	/**
	 * @param attempts The attempts to set.
	 */
	protected void setAttempts() {
		nAttempts++;
	}

	/**
	 * @return Returns the attempts.
	 */
	public String getAttempts() {
		return String.valueOf(nAttempts);
	}

	/**
	 * @return Returns the answer.
	 */
	public String getAnswer() {
		return this.answer;
	}
	
	public String getFormattedAnswer() {
		if(isFormatUserAnswer()) {
			String aaa = this.getAnswer();
			String displayFormat = NumberFormatter.getFormat(getDecimalDigits());
			return NumberFormatter.format(aaa, displayFormat);
		}
		return this.answer;
	}
	
	/**
	 * @param answer The answer to set.
	 */
	public void setAnswer(String answer) {
		this.answer = answer;
	}
	/**
	 * @return Returns the data1.
	 */
	public String getData1() {
		return data1;
	}
	/**
	 * @param data1 The data1 to set.
	 */
	public void setData1(String data1) {
		this.data1 = data1;
	}
	/**
	 * @return Returns the data2.
	 */
	public String getData2() {
		return data2;
	}
	/**
	 * @param data2 The data2 to set.
	 */
	public void setData2(String data2) {
		this.data2 = data2;
	}
	/**
	 * @return Returns the operation.
	 */
	public String getOperation() {
		return this.operation;
	}
	/**
	 * @param operation The operation to set.
	 */
	public void setOperation(String operation) {
		this.operation = operation;
	}
	/**
	 * @return Returns the operationsign.
	 */
	public String getOperationsign() {
		
		if(operation != null && operation.equals(IHandlerConstant.SUBTRACTION)) {
			return "-";
		} else if(operation != null && operation.equals(IHandlerConstant.MULTIPLICATION)) {
			return "x";
		} else if(operation != null && operation.equals(IHandlerConstant.DIVISION)) {
			return "/";
		} else if(operation != null && operation.equals(IHandlerConstant.ADDITION)) {
			return "+";
		}
		return null;
	}


	
	
	/**
	 * @return Returns the .
	 */
	public boolean isAnswered() {
		return answered;
	}
	
	/**
	 * @param answered The answered to set.
	 */
	public void setAnswered(boolean answered) {
		this.answered = answered;
	}
	
	public boolean isCorrect(String useranswer) {
		this.answered = true;
		this.useranswer = useranswer;
		nAttempts++;
		
		if(!isFormatUserAnswer()) {
			if(this.useranswer.trim().equals(this.getAnswer().trim())) {
				this.correctAnswer = true;			
				return true;
			}	
			else {
				return false;
			}
		}
		String displayFormat = NumberFormatter.getFormat(getDecimalDigits());
		String formmattedUA = NumberFormatter.format(useranswer, displayFormat);
		
		if(formmattedUA.equals(this.getFormattedAnswer())) {
			this.correctAnswer = true;			
			return true;
		}
		return false;
	}

	
	/**
	 * @return Returns the adddata.
	 */
	public List<String> getAdddata() {
		return adddata;
	}
	/**
	 * @param adddata The adddata to set.
	 */
	public void addData(String value) {
		if(this.adddata == null) {
			this.adddata = new ArrayList<String>();
		}
		this.adddata.add(value);
	}
	
	public void setAdddata(List<String> lstdata) {
		this.adddata = lstdata;
	}
	
	public String getScore() {
	    return NumberFormatter.format(String.valueOf(1.0/nAttempts), "#.#"); 
	}

	public String getQuestionNum() {
		return questionNum;
	}

	public void setQuestionNum(String questionNum) {
		this.questionNum = questionNum;
	}

	public long getStarttime() {
		return starttime;
	}

	public void setStarttime(long starttime) {
		this.starttime = starttime;
	}

	public long getEndtime() {
		return endtime;
	}

	public void setEndtime(long endtime) {
		this.endtime = endtime;
	}
	
	public String getTimeTaken() {
		long timetaken = (getEndtime() - getStarttime())/1000;
		int intval = new Long(timetaken).intValue();
		return String.valueOf(intval);
	}
	
	/**
	 * @return Returns the userAttemptLimit.
	 */
	public int getUserAttemptLimit() {
		return userAttemptLimit;
	}
	/**
	 * @param userAttemptLimit The userAttemptLimit to set.
	 */
	public void setUserAttemptLimit(int userAttemptLimit) {
		this.userAttemptLimit = userAttemptLimit;
	}
	
	
	/**
	 * @return Returns the formatUserAnswer.
	 */
	public boolean isFormatUserAnswer() {
		return formatUserAnswer;
	}
	/**
	 * @param formatUserAnswer The formatUserAnswer to set.
	 */
	public void setFormatUserAnswer(boolean formatUserAnswer) {
		this.formatUserAnswer = formatUserAnswer;
	}

	public boolean isMaxAttempts() {

		if(getAttempts().equals(String.valueOf(getUserAttemptLimit()))) {
			return true;
		}
		
		return false;
	}
	
	
	
} //ProcessHandler
